import { Config, Entity, LearningProgress, Word } from "../model"
import dict from "../dict.json"
import { unproxy } from "../utils"

let db: IDBDatabase
interface DBDate {
    config: Entity<Config>
    progresses: Entity<LearningProgress>[]
    words: Entity<Word>[]
}
let dbData: DBDate

export function getData() {
    if (!dbData) throw "db is not init"
    return dbData
}

export async function init() {
    return new Promise((initR, initJ) => {
        const openRequest = indexedDB.open("db", 1)

        if (openRequest) {
            const storeParameters = {
                keyPath: "id",
                autoIncrement: true,
            }

            let upgradePromise: Promise<any>

            openRequest.onupgradeneeded = async function () {
                let upgradeR: any, upgradeJ: any
                try {
                    upgradePromise = new Promise((r, j) => {
                        upgradeR = r
                        upgradeJ = j
                    })

                    db = this.result

                    db.createObjectStore("progress", storeParameters)

                    await new Promise((r, j) => {
                        const configStore = db.createObjectStore(
                            "config",
                            storeParameters
                        )
                        const res = configStore.add({
                            maximumNumberOfWordsBeingLearned: 10,
                            skilledProgessBaseTestInterval: 1000 * 60 * 30,
                            forgettingSpeed: 1,
                            skilledRequiredExp: 100,
                            learningSpeed: 1,
                            defaultPronounce: "us",
                            progressColors: [
                                "#00CC66",
                                "#3366FF",
                                "#9900CC",
                                "#FF9933",
                            ],
                        })
                        res.onsuccess = function () {
                            r(undefined)
                        }
                        res.onerror = function () {
                            j(this.error)
                        }
                    })

                    await new Promise(async (r, j) => {
                        try {
                            const wordStore = db.createObjectStore(
                                "word",
                                storeParameters
                            )
                            wordStore.createIndex("word", "word")

                            const promises: Promise<any>[] = []
                            dict.forEach((w) => {
                                if (!Array.isArray(w.meanings)) {
                                    const m = w.meanings
                                    w.meanings = []
                                    for (const key in m as Object) {
                                        if (
                                            Object.prototype.hasOwnProperty.call(
                                                m,
                                                key
                                            )
                                        ) {
                                            const element = m[key]
                                            w.meanings.push(element)
                                        }
                                    }
                                }
                                promises.push(
                                    new Promise((r, j) => {
                                        const res = wordStore.add(w)
                                        res.onsuccess = function () {
                                            r(undefined)
                                        }
                                        res.onerror = function () {
                                            j(this.error)
                                        }
                                    })
                                )
                            })
                            await Promise.all(promises)
                            r(undefined)
                        } catch (e) {
                            j(e)
                        }
                    })

                    upgradeR(undefined)
                } catch (e) {
                    upgradeJ(e)
                }
            }

            openRequest.onsuccess = async function () {
                if (upgradePromise) {
                    await upgradePromise
                }
                db = this.result

                let config: any, words: any, progresses: any

                await new Promise((r, j) => {
                    const res = db
                        .transaction("config")
                        .objectStore("config")
                        .getAll()

                    res.onsuccess = function () {
                        config = this.result[0]
                        r(undefined)
                    }
                    res.onerror = function () {
                        j(this.error)
                    }
                })

                await new Promise((r, j) => {
                    const res = db
                        .transaction("word")
                        .objectStore("word")
                        .getAll()

                    res.onsuccess = function () {
                        words = this.result
                        r(undefined)
                    }
                    res.onerror = function () {
                        j(this.error)
                    }
                })

                await new Promise((r, j) => {
                    const res = db
                        .transaction("progress")
                        .objectStore("progress")
                        .getAll()

                    res.onsuccess = function () {
                        progresses = this.result
                        r(undefined)
                    }
                    res.onerror = function () {
                        j(this.error)
                    }
                })

                dbData = {
                    config,
                    words,
                    progresses,
                }
                initR(undefined)
            }
        } else {
            initJ("open indexedDB error")
        }
    })
}

export async function updateConfig(config: Entity<Config>) {
    config = unproxy(config)
    return new Promise((r, j) => {
        const res = db
            .transaction("config", "readwrite")
            .objectStore("config")
            .put(config)
        res.onsuccess = function () {
            r(undefined)
        }
        res.onerror = function () {
            j(this.error)
        }
    })
}

export async function updateWord(word: Entity<Word>) {
    word = unproxy(word)
    return new Promise((r, j) => {
        const res = db
            .transaction("word", "readwrite")
            .objectStore("word")
            .put(word)
        res.onsuccess = function () {
            r(undefined)
        }
        res.onerror = function () {
            j(this.error)
        }
    })
}

export async function updateProgress(progress: Entity<LearningProgress>) {
    progress = unproxy(progress)
    return new Promise((r, j) => {
        const res = db
            .transaction("progress", "readwrite")
            .objectStore("progress")
            .put(progress)
        res.onsuccess = function () {
            r(undefined)
        }
        res.onerror = function () {
            j(this.error)
        }
    })
}

export async function addProgress(
    progress: LearningProgress
): Promise<Entity<LearningProgress>> {
    progress = unproxy(progress)
    return new Promise((r, j) => {
        const res = db
            .transaction("progress", "readwrite")
            .objectStore("progress")
            .add(progress)
        res.onsuccess = function () {
            r({
                ...progress,
                id: this.result,
            })
        }
        res.onerror = function () {
            j(this.error)
        }
    })
}
